SignalR 中的依赖关系注入 您所在的位置:网站首页 winform 依赖注入 SignalR 中的依赖关系注入

SignalR 中的依赖关系注入

2024-07-17 18:10| 来源: 网络整理| 查看: 265

SignalR 中的依赖项注入 项目06/16/2023

作者 :Patrick Fletcher

警告

本文档不适用于最新版本的 SignalR。 查看 ASP.NET Core SignalR。

本主题中使用的软件版本 Visual Studio 2013 .NET 4.5 SignalR 版本 2 本主题的早期版本

有关 SignalR 早期版本的信息,请参阅 SignalR 旧版本。

问题和评论

请留下反馈,说明你对本教程的喜爱程度,以及我们可以在页面底部的评论中改进的内容。 如果你有与本教程不直接相关的问题,可以将其发布到 ASP.NET SignalR 论坛 或 StackOverflow.com。

依赖关系注入是一种删除对象之间的硬编码依赖项的方法,可以更轻松地替换对象的依赖项,以便使用 mock 对象) 测试 (或更改运行时行为。 本教程介绍如何在 SignalR 中心上执行依赖项注入。 它还演示如何将 IoC 容器与 SignalR 配合使用。 IoC 容器是依赖关系注入的常规框架。

什么是依赖关系注入?

如果已熟悉依赖项注入,请跳过此部分。

依赖项注入 (DI) 是一种模式,其中对象不负责创建自己的依赖项。 下面是激励 DI 的简单示例。 假设你有一个需要记录消息的对象。 可以定义日志记录接口:

interface ILogger { void LogMessage(string message); }

在 对象中,可以创建 用于 ILogger 记录消息的 :

// Without dependency injection. class SomeComponent { ILogger _logger = new FileLogger(@"C:\logs\log.txt"); public void DoSomething() { _logger.LogMessage("DoSomething"); } }

这有效,但它不是最好的设计。 如果要将 替换为 FileLogger 另一个 ILogger 实现,则必须修改 SomeComponent。 假设许多其他对象都使用 FileLogger,则需要更改所有这些对象。 或者,如果决定进行 FileLogger 单一实例,还需要在整个应用程序中进行更改。

更好的方法是将 “注入” ILogger 到 对象中,例如,通过使用构造函数参数:

// With dependency injection. class SomeComponent { ILogger _logger; // Inject ILogger into the object. public SomeComponent(ILogger logger) { if (logger == null) { throw new NullReferenceException("logger"); } _logger = logger; } public void DoSomething() { _logger.LogMessage("DoSomething"); } }

现在, 对象不负责选择要使用的对象 ILogger 。 可以在不更改依赖实现的对象的情况下切换 ILogger 实现。

var logger = new TraceLogger(@"C:\logs\log.etl"); var someComponent = new SomeComponent(logger);

此模式称为 构造函数注入。 另一种模式是 setter 注入,其中通过 setter 方法或属性设置依赖项。

SignalR 中的简单依赖关系注入

请考虑使用 SignalR 入门教程中的聊天应用程序。 下面是该应用程序中的中心类:

public class ChatHub : Hub { public void Send(string name, string message) { Clients.All.addMessage(name, message); } }

假设你想要在发送聊天消息之前将聊天消息存储在服务器上。 可以定义一个接口来抽象此功能,并使用 DI 将接口注入到 类中 ChatHub 。

public interface IChatRepository { void Add(string name, string message); // Other methods not shown. } public class ChatHub : Hub { private IChatRepository _repository; public ChatHub(IChatRepository repository) { _repository = repository; } public void Send(string name, string message) { _repository.Add(name, message); Clients.All.addMessage(name, message); }

唯一的问题是 SignalR 应用程序不直接创建中心;SignalR 会为你创建它们。 默认情况下,SignalR 要求中心类具有无参数构造函数。 但是,可以轻松注册函数以创建中心实例,并使用此函数执行 DI。 通过调用 GlobalHost.DependencyResolver.Register 注册函数。

public void Configuration(IAppBuilder app) { GlobalHost.DependencyResolver.Register( typeof(ChatHub), () => new ChatHub(new ChatMessageRepository())); App.MapSignalR(); // ... }

现在,SignalR 将在需要创建 ChatHub 实例时调用此匿名函数。

IoC 容器

前面的代码适用于简单情况。 但你仍然必须写下:

... new ChatHub(new ChatMessageRepository()) ...

在具有许多依赖项的复杂应用程序中,可能需要编写大量此“接线”代码。 此代码可能难以维护,尤其是在嵌套依赖项的情况下。 单元测试也很难。

一种解决方案是使用 IoC 容器。 IoC 容器是负责管理依赖项的软件组件。向容器注册类型,然后使用容器创建对象。 容器会自动找出依赖项关系。 许多 IoC 容器还允许你控制对象生存期和范围等内容。

注意

“IoC”代表“控制反转”,这是框架调用应用程序代码的一般模式。 IoC 容器为你构造对象,从而“反转”通常的控制流。

在 SignalR 中使用 IoC 容器

聊天应用程序可能太简单,无法从 IoC 容器中受益。 我们来看看 StockTicker 示例。

StockTicker 示例定义了两个main类:

StockTickerHub:管理客户端连接的中心类。 StockTicker:保存股票价格并定期更新的单一实例。

StockTickerHub 保存对单一实例的 StockTicker 引用,而 StockTicker 保留对 的 IHubConnectionContext 的 StockTickerHub引用。 它使用此接口与 StockTickerHub 实例通信。 (有关详细信息,请参阅使用 ASP.NET SignalR.) 进行服务器广播

我们可以使用 IoC 容器来稍微解开这些依赖项。 首先,让我们简化 StockTickerHub 和 StockTicker 类。 在以下代码中,我注释掉了不需要的部分。

从 StockTickerHub中删除无参数构造函数。 相反,我们将始终使用 DI 来创建中心。

[HubName("stockTicker")] public class StockTickerHub : Hub { private readonly StockTicker _stockTicker; //public StockTickerHub() : this(StockTicker.Instance) { } public StockTickerHub(StockTicker stockTicker) { if (stockTicker == null) { throw new ArgumentNullException("stockTicker"); } _stockTicker = stockTicker; } // ...

对于 StockTicker,请删除单一实例。 稍后,我们将使用 IoC 容器来控制 StockTicker 生存期。 此外,将构造函数设为公共。

public class StockTicker { //private readonly static Lazy _instance = new Lazy( // () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext().Clients)); // Important! Make this constructor public. public StockTicker(IHubConnectionContext clients) { if (clients == null) { throw new ArgumentNullException("clients"); } Clients = clients; LoadDefaultStocks(); } //public static StockTicker Instance //{ // get // { // return _instance.Value; // } //}

接下来,可以通过为 StockTicker创建接口来重构代码。 我们将使用此接口将 与 StockTicker 类分离StockTickerHub。

Visual Studio 使这种重构变得简单。 打开文件 StockTicker.cs,右键单击 StockTicker 类声明,然后选择“ 重构 ...” 提取接口。

在 “提取接口 ”对话框中,单击“ 全选”。 保留其他默认值。 单击 “确定” 。

Visual Studio 将创建名为 IStockTicker的新接口,并将 更改为 StockTicker 从 IStockTicker派生。

打开文件 IStockTicker.cs,并将接口更改为 公共接口。

public interface IStockTicker { void CloseMarket(); IEnumerable GetAllStocks(); MarketState MarketState { get; } void OpenMarket(); void Reset(); }

StockTickerHub在 类中,将 的StockTicker两个实例更改为 IStockTicker:

[HubName("stockTicker")] public class StockTickerHub : Hub { private readonly IStockTicker _stockTicker; public StockTickerHub(IStockTicker stockTicker) { if (stockTicker == null) { throw new ArgumentNullException("stockTicker"); } _stockTicker = stockTicker; }

IStockTicker创建接口并非严格必要,但我想展示 DI 如何帮助减少应用程序中组件之间的耦合。

添加 Ninject 库

有许多适用于 .NET 的开源 IoC 容器。 在本教程中,我将使用 Ninject。 (其他热门库包括 城堡温莎、 Spring.Net、 Autofac、 Unity 和 StructureMap.)

使用 NuGet 包管理器安装 Ninject 库。 在 Visual Studio 中,从“ 工具 ”菜单中选择“ NuGet 包管理器>包管理器控制台”。 在“Package Manager Console”窗口中,输入以下命令:

Install-Package Ninject -Version 3.0.1.10 替换 SignalR 依赖项解析程序

若要在 SignalR 中使用 Ninject,请创建派生自 DefaultDependencyResolver 的类。

internal class NinjectSignalRDependencyResolver : DefaultDependencyResolver { private readonly IKernel _kernel; public NinjectSignalRDependencyResolver(IKernel kernel) { _kernel = kernel; } public override object GetService(Type serviceType) { return _kernel.TryGet(serviceType) ?? base.GetService(serviceType); } public override IEnumerable GetServices(Type serviceType) { return _kernel.GetAll(serviceType).Concat(base.GetServices(serviceType)); } }

此类替代 DefaultDependencyResolver 的 GetService 和 GetServices 方法。 SignalR 调用这些方法以在运行时创建各种对象,包括中心实例,以及 SignalR 内部使用的各种服务。

GetService 方法创建类型的单个实例。 重写此方法以调用 Ninject 内核的 TryGet 方法。 如果该方法返回 null,则回退到默认冲突解决程序。 GetServices 方法创建指定类型的对象的集合。 重写此方法,将 Ninject 的结果与默认解析程序的结果连接在一起。 配置 Ninject 绑定

现在,我们将使用 Ninject 声明类型绑定。

打开应用程序的 Startup.cs 类 (,该类根据 中的 readme.txt包说明手动创建,或通过向项目添加身份验证) 创建。 在 方法中 Startup.Configuration ,创建 Ninject 容器,由 Ninject 调用 内核。

var kernel = new StandardKernel();

创建自定义依赖项解析程序的实例:

var resolver = new NinjectSignalRDependencyResolver(kernel);

为 IStockTicker 创建绑定,如下所示:

kernel.Bind() .To() // Bind to StockTicker. .InSingletonScope(); // Make it a singleton object.

此代码说了两件事。 首先,每当应用程序需要 时 IStockTicker,内核都应创建 的 StockTicker实例。 其次,类 StockTicker 应是作为单一实例对象创建的 。 Ninject 将创建 对象的一个实例,并为每个请求返回相同的实例。

为 IHubConnectionContext 创建绑定,如下所示:

kernel.Bind(typeof(IHubConnectionContext)).ToMethod(context => resolver.Resolve().GetHubContext().Clients ).WhenInjectedInto();

此代码创建返回 IHubConnection 的匿名函数。 WhenInjectedInto 方法告知 Ninject 仅在创建IStockTicker实例时使用此函数。 原因是 SignalR 在内部创建 IHubConnectionContext 实例,我们不希望重写 SignalR 创建它们的方式。 此函数仅适用于我们的 StockTicker 类。

通过添加中心配置,将依赖项解析程序传递到 MapSignalR 方法:

var config = new HubConfiguration(); config.Resolver = resolver; Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config);

使用新参数更新示例的 Startup 类中的 Startup.ConfigureSignalR 方法:

public static void ConfigureSignalR(IAppBuilder app, HubConfiguration config) { app.MapSignalR(config); }

现在,SignalR 将使用 MapSignalR 中指定的冲突解决程序,而不是默认冲突解决程序。

下面是 的完整 Startup.Configuration代码列表。

public class Startup { public void Configuration(IAppBuilder app) { // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888 var kernel = new StandardKernel(); var resolver = new NinjectSignalRDependencyResolver(kernel); kernel.Bind() .To() // Bind to StockTicker. .InSingletonScope(); // Make it a singleton object. kernel.Bind(typeof(IHubConnectionContext)).ToMethod(context => resolver.Resolve().GetHubContext().Clients ).WhenInjectedInto(); var config = new HubConfiguration(); config.Resolver = resolver; Microsoft.AspNet.SignalR.StockTicker.Startup.ConfigureSignalR(app, config); } }

若要在 Visual Studio 中运行 StockTicker 应用程序,请按 F5。 在浏览器窗口中,导航到 http://localhost:*port*/SignalR.Sample/StockTicker.html。

应用程序的功能与之前完全相同。 (有关说明,请参阅 使用 ASP.NET SignalR 进行服务器广播) 我们尚未更改行为;只是使代码更易于测试、维护和改进。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有